package it.unipd.dei.es1011.nucularc0d3.defaultproject;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.BlurMaskFilter.Blur;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.view.Display;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

/**
 * This class need to know from Intent
 * - seekBarTime -> how much time to record
 * - spinnerSelected -> sampling rate
 * - checkboxPrefX -> if X axis is selected
 * - checkboxPrefY -> same
 * - checkboxPrefZ -> same
 * 
 * if no parameter is passed the class will load the preferences saved from the Settings.
 * @author Luca Lazzarini
 *
 */
public class Recording extends Activity {
	//	used for store min/max values
	private AtomicReference<Float> xAxisMaxValue;
	private AtomicReference<Float> yAxisMaxValue;
	private AtomicReference<Float> zAxisMaxValue;

	private AtomicReference<Float> xAxisMinValue;
	private AtomicReference<Float> yAxisMinValue;
	private AtomicReference<Float> zAxisMinValue;

	//	Details value (will be passed to the service)
	private int samplingRate;
	private boolean[] axis = new boolean[3];
	private long totalTime;
	private AtomicLong numberSampleCollected = new AtomicLong(0);

	//	view elements
	private TextView sampleRateTV;
	private TextView sampleNumberTV;
	private TextView elapsedTimeValueTV;
	private TextView totalTimeValueTV;
	private Button myStopButton;
	private LinearLayout graphLayout;
	private GraphView myGraphView;
	private Paint myPaint;

	//	sensor
	private SensorManager mySensorManager;
	private Sensor mySensor;

	//	For messenger
	private Messenger mService = null;
	private boolean mIsBound;
	private final Messenger mMessenger = new Messenger(new IncomingHandler());
	
	/**
	 * Standard override to get key-press events.
	 * I will delete the connection to the service, the service will stop when it is unbind.
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent msg) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			doUnbindService();
			stopService(new Intent(this, RecordingService.class));
			startActivity(new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); 
			return true;
		}
		return super.onKeyDown(keyCode, msg);
	}

	//	connection with the Recording Service
	private ServiceConnection mConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			mService = new Messenger(service);
			try {
				Message msg = Message.obtain(null, RecordingService.REGISTER_CLIENT);
				msg.replyTo = mMessenger;
				mService.send(msg);
			} catch (RemoteException e) {
				// In this case the service has crashed before we could even do anything with it
			}
		}

		public void onServiceDisconnected(ComponentName className) {
			// This is called when the connection with the service has been unexpectedly disconnected - process crashed.
			mService = null;
		}
	};
	

	/**
	 * This class is used to handle the messages that come from the service.
	 * @author Luca Lazzarini
	 *
	 */
	class IncomingHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case RecordingService.MSG_SENDING_SAMPLE_COLLECTED:
				numberSampleCollected.set(msg.arg1);
				sampleRateTV.setText("" + msg.arg1);
				break;
			case RecordingService.MSG_SENDING_ELAPSED_TIME:
				String str1 = msg.getData().getString("elapsedTime");
				elapsedTimeValueTV.setText("" + str1);
				break;
			case RecordingService.MSG_STOP_SERVICE:
				// the service need to be stopped, starting the stop procedure
				doUnbindService(); 
				stopService(new Intent(Recording.this, RecordingService.class));
				mySensorManager.unregisterListener(myGraphView); // with this i STOP the sensor
				
				Intent intentToRecordingEnded = new Intent(getBaseContext(),RecordingEnded.class);
				float[] axisMaxValue = {xAxisMaxValue.get(),yAxisMaxValue.get(),zAxisMaxValue.get()};
				float[] axisMinValue = {xAxisMinValue.get(),yAxisMinValue.get(),zAxisMinValue.get()};
				intentToRecordingEnded.putExtra("axisMaxValue", axisMaxValue); // put value into array
				intentToRecordingEnded.putExtra("axisMinValue", axisMinValue);
				intentToRecordingEnded.putExtra("numberSampleCollected", numberSampleCollected.get());
				intentToRecordingEnded.putExtra("totalTime", msg.getData().getString("duration"));
				intentToRecordingEnded.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // TODO
				startActivity(intentToRecordingEnded);
				finish(); // prevent this activity to be put in the stack
				break;
			default:
				super.handleMessage(msg);
			}
		}
	}

	/**
	 * Create bind with the service, passing it the specific. If no specific is sent via the intent
	 * it will upload the specific saved into sharedPreferences (used in New Quick Session).
	 */
	void doBindService(){
		if(getIntent().hasExtra("axis"))
			axis = getIntent().getBooleanArrayExtra("axis");
		else{
			SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
			axis[0] = (preferences.getBoolean("checkboxPrefX", true));
			axis[1] = (preferences.getBoolean("checkboxPrefY", false));
			axis[2] = (preferences.getBoolean("checkboxPrefZ", false));
		}
		if(getIntent().hasExtra("seekBarTime"))
			totalTime = ((getIntent().getIntExtra("seekBarTime",0)+1) * 60000);
		else{
			SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
			totalTime = (preferences.getInt("seekBarTime", 0)+1)* 60000;
		}

		Resources res = getResources();
		if(getIntent().hasExtra("spinnerSelected")){
			samplingRate = new Integer(res.getStringArray(R.array.samplingRateValues)[getIntent().getIntExtra("spinnerSelected",1)]);
		}
			
		else{
			SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
			samplingRate = new Integer(preferences.getString("samplingRate", 700+""));
		}
		
		if(axis[0] || axis[1] || axis[2]){
			Intent tempIntent = new Intent(this, RecordingService.class);
			
			tempIntent.putExtra("axis",axis);
			tempIntent.putExtra("samplingRate", samplingRate);
			tempIntent.putExtra("totalTime", totalTime);
			startService(tempIntent);
		}
		else{
			Toast.makeText(getApplicationContext(), 
				    "An axis must be selected!!", 
				    Toast.LENGTH_LONG).show();
			startActivity(new Intent(this, MainActivity.class)); 
		}
	}

	/**
	 * Destroy the bind with the service.
	 */
	void doUnbindService(){
		if (mIsBound) {
			// If we have received the service, and hence registered with it, then now is the time to unregister.
			if (mService != null) {
				try {
					Message msg = Message.obtain(null, RecordingService.UNREGISTER_CLIENT);
					msg.replyTo = mMessenger;
					mService.send(msg);
				} catch (RemoteException e) {
					// There is nothing special we need to do if the service has crashed.
				}
			}
			// Detach our existing connection.
			unbindService(mConnection);
			mIsBound = false;
		}
	}

	/**
	 * Here i initialize my first elements for recording, like temp value for axis recording, end time ecc.
	 */
	private void initSystemForRecording(){
		xAxisMaxValue = new AtomicReference<Float>(0.0f);
		yAxisMaxValue = new AtomicReference<Float>(0.0f);
		zAxisMaxValue = new AtomicReference<Float>(0.0f);

		xAxisMinValue = new AtomicReference<Float>(0.0f);
		yAxisMinValue = new AtomicReference<Float>(0.0f);
		zAxisMinValue = new AtomicReference<Float>(0.0f);
	}

	/**
	 * Here i initialize my Accelerometer
	 */
	private void initSensor(){
		mySensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
		mySensor = mySensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
	}

	/**
	 * Here i get a reference of the elements inside the View
	 */
	private void initViewElements(){

		sampleNumberTV = (TextView) findViewById(R.id.sampleCollectedItemTextView);
		sampleRateTV = (TextView) findViewById(R.id.sampleCollectedItemValueTextView);

		elapsedTimeValueTV = (TextView) findViewById(R.id.elapsedTimeValueTextView);

		totalTimeValueTV = (TextView) findViewById(R.id.totalTimeValueTextView);

		myStopButton  = (Button) findViewById(R.id.stopButton);
		myStopButton.setOnClickListener(stopRecording);
		graphLayout = (LinearLayout) findViewById(R.id.graph);

		// retrieve the info to set the correct text into sampling rate (Fast,Normal,Slow)
		Resources res = getResources();
		String[] spinnerString = res.getStringArray(R.array.samplingRateString);
		String[] spinnerNumber = res.getStringArray(R.array.samplingRateValues);
		for(int i = 0; i < spinnerNumber.length; i++)
			if(new Integer(spinnerNumber[i]) == samplingRate)
				sampleNumberTV.setText("Sample ("+spinnerString[i]+"):");
		
		totalTimeValueTV.setText(""+(int)((totalTime/1000)/60)+" m "+(int)((totalTime/1000)%60)+" s");

		myPaint = new Paint();
		myGraphView = new GraphView(this);
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.recording); // setting layout
		
		initSystemForRecording();
		initSensor();
		doBindService();
		initViewElements();
		
		/** show short instructions */
		Toast.makeText(getApplicationContext(), 
				getResources().getString(R.string.recording_abort), 
				Toast.LENGTH_LONG).show();
	}

	/**
	 * If the activity is stopped/send to background or interrupted i must stop the 
	 * sensor listener and unbind the service.
	 */
	@Override
	protected void onPause() {
		super.onPause();
		// disabling the sensor's listener for power saving.
		mySensorManager.unregisterListener(myGraphView); // with this i will STOP the sensor
		try {
			doUnbindService();
		} catch (Throwable t) {
		}
	}

	@Override
	protected void onResume() {
		super.onResume();
		graphLayout.removeAllViews(); // remove the previous view
		graphLayout.addView(myGraphView);
		mySensorManager.registerListener(
				myGraphView, mySensor, 
				SensorManager.SENSOR_DELAY_GAME);
		bindService(new Intent(this, RecordingService.class), mConnection, Context.BIND_AUTO_CREATE);
		mIsBound = true;
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		try {
			doUnbindService();
		} catch (Throwable t) {
		}
	}

	/**
	 * Class that implements the listener for the Accelerometer, it will also draw the input of the Accelerometer.
	 * This class will create the live graph that show the actual value of the accelerometer axis.
	 */
	private class GraphView extends View implements SensorEventListener{

		private Bitmap myBitmap;
		private Canvas myCanvas = new Canvas(); 
		private float scalingFactor;
		private int zero; // y coord for 0 G line (canvasX / 2)
		private int textHeaderSize; // size of the text X Axis, Y Axis and Z Axis
		private int paddingTopTextHeader; // used to place the text inside the drawing area
		private int textValueSize; // size of the text that show the actual value of acceleration
		private int canvasX; // drawing area
		private int canvasY; // drawing area
		private int circleRadius; // radius of the circle 
		private int startPosition; // base position for X axis circle
		private int positionOffset; // offset between axis circle
		private float maxGValue = 2.0f; // maximum G Force recorded
		private float maxValueRecorded = SensorManager.GRAVITY_EARTH * maxGValue;
		private BlurMaskFilter myBlurMaskFilter = new BlurMaskFilter(3.0f, Blur.INNER); // shader effect for the spheres

		public GraphView(Context context){
			super(context);
			Display display = getWindowManager().getDefaultDisplay();
			// define my drawing space
			// portait 
			if(getWindowManager().getDefaultDisplay().getRotation () ==  Surface.ROTATION_0){
				canvasX = display.getWidth();
				canvasY = (int) (display.getHeight() / 2.0f);
			} // landscape
			else{
				canvasY = display.getHeight();
				canvasX = (int) (display.getWidth() / 2.0f);
			}
			
			// define positioning of text and axis ball and relative offset
			positionOffset = startPosition = (int) canvasX / 4;
			// define space between line of G value
			scalingFactor = (canvasY * 0.65f) / (4 * SensorManager.GRAVITY_EARTH);
			// define size of balls
			circleRadius = (int) canvasX / 24;
			// define text size and padding
			textHeaderSize = (int) canvasX / 21;
			paddingTopTextHeader = ((int) canvasX / 22) + 5;
			textValueSize = (int) canvasX / 21;
			// define 0 G line
			zero = canvasY / 2;
			myBitmap = Bitmap.createBitmap(canvasX, canvasY, Bitmap.Config.RGB_565);
			myCanvas.setBitmap(myBitmap);
			myPaint.setAntiAlias(true);
		}

		@Override 
		protected void onDraw(Canvas canvas) {
			super.onDraw(canvas);
			canvas.drawBitmap(myBitmap,0,0,null);
		}

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// no need to implement
		}

		@Override
		public void onSensorChanged(SensorEvent event) {
			//	normalizing output within the range +- maxGValue G-Force
			if(event.values[0] > maxValueRecorded)
				event.values[0] = maxValueRecorded;
			if(event.values[0] < (-maxValueRecorded))
				event.values[0] = -maxValueRecorded;
			if(event.values[1] > maxValueRecorded)
				event.values[1] = maxValueRecorded;
			if(event.values[1] < (-maxValueRecorded))
				event.values[1] = -maxValueRecorded;
			if(event.values[2] > maxValueRecorded)
				event.values[2] = maxValueRecorded;
			if(event.values[2] < (-maxValueRecorded))
				event.values[2] = -maxValueRecorded;

			// updating Max/Min values
			if(xAxisMaxValue.get() <  event.values[0])
				xAxisMaxValue.set(event.values[0]);
			if(yAxisMaxValue.get() <  event.values[1])
				yAxisMaxValue.set(event.values[1]);
			if(zAxisMaxValue.get() <  event.values[2])
				zAxisMaxValue.set(event.values[2]);

			if(xAxisMinValue.get() >  event.values[0])
				xAxisMinValue.set(event.values[0]);
			if(yAxisMinValue.get() >  event.values[1])
				yAxisMinValue.set(event.values[1]);
			if(zAxisMinValue.get() >  event.values[2])
				zAxisMinValue.set(event.values[2]);

			myCanvas.drawColor(Color.WHITE); // make the background

			//	Setting up the grid
			myPaint.setTextSize(textHeaderSize);
			//	myPaint.setColor(Color.GREEN);
			myCanvas.drawText("X Axis", startPosition - 15, paddingTopTextHeader, myPaint); // 0 index
			//	myPaint.setColor(Color.YELLOW);
			myCanvas.drawText("Y Axis", startPosition + positionOffset - 15, paddingTopTextHeader, myPaint); // 0 index
			//	myPaint.setColor(Color.RED);
			myCanvas.drawText("Z Axis", startPosition + (positionOffset * 2) - 15, paddingTopTextHeader, myPaint); // 0 index

			myPaint.setColor(Color.BLACK);
			myPaint.setTextSize(textValueSize);
			myCanvas.drawLine(0, zero, canvasX, zero, myPaint); // 0 line
			myCanvas.drawText("0 G", 10, -3 + zero, myPaint); // 0 index

			// setting line +-maxGValue G-Force
			for(int o = 1; o <= maxGValue; o++){
				myCanvas.drawText("-"+ o +" G", 10, -3 + zero + (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), myPaint);
				myCanvas.drawText(""+ o +" G", 10, -3 + zero - (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), myPaint);
				//	negative G
				myCanvas.drawLine(0, zero + (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), canvasX, zero + (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), myPaint);
				//	positive G
				myCanvas.drawLine(0, zero - (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), canvasX, zero - (SensorManager.GRAVITY_EARTH * (scalingFactor * o)), myPaint); 
			}

			//	drawing results on screen
			if(axis[0]){				
				myPaint.setColor(Color.RED);
				myPaint.setMaskFilter(myBlurMaskFilter);
				myCanvas.drawCircle(startPosition, zero - (event.values[0] * scalingFactor), circleRadius, myPaint);
				myPaint.setColor(Color.BLACK);
				myPaint.setMaskFilter(null);
				myCanvas.drawText(""+event.values[0], startPosition - 10, canvasY - 10 , myPaint);
			}

			if(axis[1]){
				myPaint.setColor(Color.GREEN);
				myPaint.setMaskFilter(myBlurMaskFilter);
				myCanvas.drawCircle(startPosition + positionOffset, zero - (event.values[1] * scalingFactor), circleRadius, myPaint);
				myPaint.setColor(Color.BLACK);
				myPaint.setMaskFilter(null);
				myCanvas.drawText(""+event.values[1], startPosition + positionOffset - 10, canvasY - 10  , myPaint);
			}

			if(axis[2]){
				myPaint.setColor(Color.BLUE);
				myPaint.setMaskFilter(myBlurMaskFilter);
				myCanvas.drawCircle(startPosition + (positionOffset * 2), zero - (event.values[2] * scalingFactor), circleRadius, myPaint);
				myPaint.setColor(Color.BLACK);
				myPaint.setMaskFilter(null);
				myCanvas.drawText(""+event.values[2], startPosition + (positionOffset * 2) - 10, canvasY - 10 , myPaint);
			}
			invalidate();
		}
	}

	/**
	 * Listener used in the stop button to stop recording.
	 */
	OnClickListener stopRecording = new OnClickListener() {
		public void onClick(View v) {
			stopServiceFromButton();
		}
	};

	/**
	 * This will send a stop request to the service
	 */
	private void stopServiceFromButton() {
		if (mIsBound) {
			if (mService != null) {
				try {
					Message msg = Message.obtain(null, RecordingService.STOP_RECORDING, 0, 0);
					msg.replyTo = mMessenger;
					mService.send(msg);
				} catch (RemoteException e) {
				}
			}
		}
	}
}